use super::sensor;
use crate::cp_drop::DropErr;
use cmsis_rtos2_rs::os_delay;
use driver_utils::service::motor::Dir;
use driver_utils::service::motor::Motor;
use driver_utils::service::motor::MotorDrv;
use periph_utils::stm32f1;
use periph_utils::AdcInit;
use periph_utils::DmaConfig;
use periph_utils::GpioConfig;
use periph_utils::PeriphReg;
use utils::defer_ref;
use utils::UninitCell;

#[derive(GpioConfig, AdcInit, DmaConfig)]
#[gpio(
    init = "gpio_init",
    io(name = "dir", io = "PC8", mode = "OUT_PP", set, reset),
    io(name = "resrved", io = "PC9", mode = "OUT_PP", set, reset),
    io(name = "cut", io = "PC10", mode = "OUT_PP", set, reset),
    io(name = "film", io = "PC11", mode = "OUT_PP", set, reset),
    io(name = "analog", io = "PC2", mode = "AIN")
)]
#[adc_config(adc = 1, chs = [12])]
#[dma(
    init = "dma_init",
    ch(name = "adc", dma = 1, ch = 1, size = 16, dir = "read", circ)
)]
struct Init;

#[derive(PeriphReg)]
#[periph(periph = "ADC1")]
struct ADC;

static ADC_DATA_BUF: UninitCell<[u16; 1]> = UninitCell::const_new();

const FILM_TIMEOUT_MS: u32 = 10 * 1000;

pub unsafe fn init() {
    Init::gpio_init();
    Init::dma_init();
    Init::adc_init();
    Init::adc_dma1_ch1_read(
        ADC_DATA_BUF.as_ptr() as _,
        ADC.dr.as_ptr() as _,
        ADC_DATA_BUF.len() as _,
    );
    Init::adc_start();
}

pub fn analog_value() -> u16 {
    ADC_DATA_BUF[0]
}

type CutFilmMotor = Motor<CutFilmCtrl>;

pub fn cut_film_top() -> Result<(), DropErr> {
    CutFilmMotor::run_to_ccw_origin()
}

pub fn cut_film_down() -> Result<(), DropErr> {
    CutFilmMotor::run_to_cw_origin()
}

pub fn film() -> Result<(), DropErr> {
    if !sensor::is_film_checked() {
        return Err(DropErr::FilmDotCup);
    }
    let mut time_count = 0; 
    Init::film_set();
    defer_ref!({
        Init::film_reset()
    });
    
    os_delay(800);
    loop {
        if sensor::is_film_sw() {
            return Ok(());
        }
        os_delay(50);
        time_count += 50;
        if time_count >= FILM_TIMEOUT_MS {
            return Err(DropErr::FilmTimeout);
        }
    }
}

struct CutFilmCtrl;

impl MotorDrv for CutFilmCtrl {

    #[inline(always)]
    fn analog_value() -> u16 {
        analog_value()
    }

    #[inline(always)]
    fn delay(tick: u32) {
        os_delay(tick);
    }

    #[inline(always)]
    fn is_ccw_origin() -> bool {
        sensor::is_cut_top()
    }

    #[inline(always)]
    fn is_cw_origin() -> bool {
        sensor::is_cut_bottom()
    }

    #[inline(always)]
    fn set_dir(dir: Dir) {
        match dir {
            Dir::CW => {
                Init::cut_set();
                Init::dir_reset();
            }
            Dir::CCW => {
                Init::cut_set();
                Init::dir_set();
            }
            Dir::Brake => {
                Init::cut_reset();
                Init::dir_reset();
            }
        }
    }

    type Error = DropErr;
    const TIMEOUT_ERR: Self::Error = DropErr::CutCupTimeout;
    const ALM_ERR: Self::Error = DropErr::CutCupAlm;
    const ALM_VALUE: u16 = 2500;
    const ALM_COUNT: u32 = 3;
}
